from vn_local.app.market_maker import CtaEngine
from vn_local.app.market_maker.template import CtaTemplate
from vn_local.app.market_maker.base import StopOrder
from vn_local.trader.object import (
    TickData,
    BarData,
    OrderTradeData, QueryOpenOrderRequest,
    PublicTradeData, SubscribeRequest, Exchange, Direction, Status, OrderType, Product
)
from vn_local.trader.utility import round_to, extract_vt_symbol
import numpy as np
from scipy.stats import t
import math
import time
import random
import numpy as np
from datetime import datetime
from typing import Dict


def get_sigma_from_log_dist(abnormal_value=400.0, miu=100.0):
    sigma = (np.log(abnormal_value) - np.log(miu)) / 3
    return sigma


def get_value_from_log_dist(miu, sigma, df):
    random = t.rvs(df, loc=miu, scale=sigma)
    v = math.exp(random)
    return v


def generate_order_volume(miu, sigma, df, p):
    res = np.random.binomial(n=1, p=p)
    if res == 1:
        return get_value_from_log_dist(miu, sigma, df)
    else:
        return 0


def get_current_date():
    now = datetime.now()
    current_date = now.date()
    return current_date


class TakerStrategy(CtaTemplate):
    """"""
    author = "CC Chen"

    target_volume_per_day = 0
    abnormal_volume_multipliers = {}
    dfs = {}
    occur_rate = 0.0
    occur_rate_on_widen = 0.0
    payup = 0
    use_taker_init = False
    max_aged_seconds = 0
    query_open_order_threshold = 0

    volumes = []
    miu = 0
    sigma = 0
    df = 0
    last_triggered_date = None
    widening = False

    parameters = ["target_volume_per_day", "abnormal_volume_multipliers", "dfs",
                  "occur_rate", "occur_rate_on_widen", "payup", "use_taker_init", "max_aged_seconds",
                  "query_open_order_threshold"]

    variables = ["miu", "sigma", "df", "latest_order", "widening"]

    def __init__(self, cta_engine, strategy_name, vt_symbol, setting):
        """"""
        super().__init__(cta_engine, strategy_name, vt_symbol, setting)
        self.symbol = self.vt_symbol.split(".")[0].split("-")[0]

        self.pricetick = 0
        self.lot_size = 0

        self.p_fit = 0
        self.direction = None
        self.latest_order = {}

        self.minimum_buy_price = 0
        self.maximum_sell_price = 0

        self.clean_count = 0
        self.recovery_count = 0
        self.query_open_order_count = 0

    def on_init(self):
        """
        Callback when strategy is inited.
        """
        self.write_log("策略初始化")
        self.parameters = self.get_parameters()
        self.update_miu_sigma()
        # print(self.parameters)
        self.write_log(f"策略初始化,当前parameters = {self.parameters}")

    def on_start(self):
        """
        Callback when strategy is started.
        """
        self.write_log("策略启动")
        self.pricetick = self.get_pricetick()
        self.lot_size = self.get_lot_size()
        self.write_log(f"[on_start]: update pricetick={self.pricetick}, lot_size={self.lot_size}")
        # symbol, exchange = extract_vt_symbol(self.vt_symbol)
        # req = QueryOpenOrderRequest(symbol=symbol, exchange=Exchange.BTSE, product=Product.SPOT)
        # self.cta_engine.query_open_orders(req)

    def update_miu_sigma(self):
        # 1h
        avg_volume_on_1h = self.target_volume_per_day / 24
        abnormal_volume_on_1h = avg_volume_on_1h * self.abnormal_volume_multipliers.get("1h", None)
        df_1h = self.dfs.get("1h", None)
        miu_on_1h = np.log(avg_volume_on_1h)
        sigma_on_1h = get_sigma_from_log_dist(abnormal_volume_on_1h, avg_volume_on_1h)
        # 1m
        target_volume_per_1h = get_value_from_log_dist(miu_on_1h, sigma_on_1h, df_1h)

        avg_volume_on_1m = target_volume_per_1h / 60
        abnormal_volume_on_1m = avg_volume_on_1m * self.abnormal_volume_multipliers.get("1m", None)
        df_1m = self.dfs.get("1m", None)
        miu_on_1m = np.log(avg_volume_on_1m)
        sigma_on_1m = get_sigma_from_log_dist(abnormal_volume_on_1m, avg_volume_on_1m)
        # 1s
        target_volume_per_1m = get_value_from_log_dist(miu_on_1m, sigma_on_1m, df_1m)

        occur_rate = self.occur_rate_on_widen if self.widening else self.occur_rate
        avg_volume_on_1s = target_volume_per_1m / 60 / occur_rate
        abnormal_volume_on_1s = avg_volume_on_1s * self.abnormal_volume_multipliers.get("1s", None)
        self.df = self.dfs.get("1s", None)
        self.miu = np.log(avg_volume_on_1s)
        self.sigma = get_sigma_from_log_dist(abnormal_volume_on_1s, avg_volume_on_1s)

    def on_interval(self):
        if self.querying_open_orders:
            # 如果查询中, 开启定时器
            self.query_open_order_count += 1

        if self.query_open_order_count >= self.query_open_order_threshold:
            self.write_log(f"[on_query_open_order: timeout]: need to retry")
            self.querying_open_orders = False
            self.open_orders: Dict[str, OrderTradeData] = {}
            self.query_open_order_count = 0

        if self.to_recover_queue and not self.querying_open_orders:
            msg = f"action:query_open_orders"
            self.write_log(msg)
            symbol, exchange = extract_vt_symbol(self.vt_symbol)
            req = QueryOpenOrderRequest(symbol=symbol, exchange=Exchange.BTSE, product=Product.SPOT)
            self.cta_engine.query_open_orders(req)
            self.querying_open_orders = True

        if not self.trading:
            # self.write_log("交易已关闭")
            return

        if self.clean_count >= self.max_aged_seconds:
            self.clean_expired_order_trades(self.max_aged_seconds)

        # 生成订单量
        if not self.maximum_sell_price or not self.minimum_buy_price:
            msg = f"No price limit, sell = {self.maximum_sell_price}, buy = {self.minimum_buy_price}"""
            self.write_log(msg)
            return

        self.update_miu_sigma()
        occur_rate = self.occur_rate_on_widen if self.widening else self.occur_rate
        order_notional_volume = generate_order_volume(self.miu, self.sigma, self.df, occur_rate)
        if not order_notional_volume:
            # self.write_log("no order notional volume")
            return

        # volume
        if not self.p_fit:
            self.write_log("no p_fit generated")
            return

        #
        # if self.use_taker_init:
        #     if not self.maker_started:
        #         order_volume = self.lot_size
        #         order_price = self.init_price
        #         order_direction = Direction.LONG if self.init_direction == 1 else Direction.SHORT
        #         self.send_order(order_direction, order_price, order_volume, poc=False, ref="init_taker")
        #     else:
        #         self.use_taker_init = False
        #
        # else:

        origin_order_volume = order_notional_volume / self.p_fit
        order_volume = max(round_to(origin_order_volume, self.lot_size), self.lot_size)
        # print(f"order_notional{order_notional_volume}, p_fit={self.p_fit}, order_volume = {order_volume}")
        # price
        order_price = max(self.minimum_buy_price + self.payup * self.pricetick,
                          self.p_fit + self.payup * self.pricetick) \
            if self.direction == Direction.LONG else (
            min(self.maximum_sell_price - self.payup * self.pricetick, self.p_fit - self.payup * self.pricetick))
        # order_price = self.p_fit + self.payup * self.pricetick if self.direction == Direction.LONG else self.p_fit - self.payup * self.pricetick
        # print(f"pfit={self.p_fit},下单order_volume{order_volume},下单价格{order_price}")
        self.send_order(self.direction, order_price, order_volume, OrderType.FAK, ref=f"taker")

        self.latest_order["direction"] = self.direction.value
        self.latest_order["price"] = order_price
        self.latest_order["volume"] = order_volume
        self.put_event()

    def on_strategy_data(self, data: dict) -> None:
        # print(f"on_taker_strategy: {data}")
        # risk strategy 的变动
        if 'widen_alerts' in data.get('variables', {}):
            widen_alerts = data['variables']['widen_alerts']
            if any(widen_alerts.values()):
                self.widening = True
            else:
                self.widening = False

        # maker strategy 来的 变动
        if "p_fit" not in data["variables"]:
            return
        latest_p_fit = data["variables"]["p_fit"]
        # if data["parameters"]["maker_init"]:
        #     self.maker_started = True

        if not self.direction:
            self.direction = Direction.LONG
            # print(f"p_fit change to {latest_p_fit}")
            self.p_fit = latest_p_fit
            return

        # self.direction = (
        #     Direction.LONG if latest_p_fit > self.p_fit else
        #     Direction.SHORT if latest_p_fit < self.p_fit else
        #     random.choice([Direction.LONG, Direction.SHORT])
        # )
        self.direction = random.choice([Direction.LONG, Direction.SHORT])
        # print(f"上一次p_fit: {self.p_fit}, 这一次P_fit: {latest_p_fit}, direction{self.direction.value}")
        self.p_fit = latest_p_fit
        # print(f"p_fit change to {latest_p_fit}")

    def on_stop(self):
        """
        Callback when strategy is stopped.
        """
        self.write_log("策略停止")

    def on_tick(self, tick: TickData):
        """
        Callback of new tick data update.
        """
        if tick.vt_symbol == self.vt_symbol:
            self.minimum_buy_price = tick.ask_price_1
            self.maximum_sell_price = tick.bid_price_1
            self.write_log(f"tick_ask={tick.ask_price_1}, tick_bid={tick.bid_price_1}, "
                           f"minimum_buy_price={self.minimum_buy_price}, maximum_sell_price={self.maximum_sell_price}")

    def on_order_trade(self, order_trade: OrderTradeData):
        """
        Callback of new order data update.
        """
        trade_volume = order_trade.trade_diff
        latest_order_trade = self.order_trades.get(order_trade.vt_orderid, None)

        if latest_order_trade:
            order_trade.cancel_num = latest_order_trade.cancel_num
            order_trade.query_num = latest_order_trade.query_num
            order_trade.latest_cancel_time = latest_order_trade.latest_cancel_time
            order_trade.latest_query_time = latest_order_trade.latest_query_time
            if not order_trade.is_exchange_status():
                order_trade.exchange_status = latest_order_trade.exchange_status
            else:
                order_trade.exchange_status = order_trade.status

        self.order_trades[order_trade.vt_orderid] = order_trade

        if trade_volume:
            msg = "ORDER FILLED[{}]，{}，{}，{}@{},ref={}".format(
                order_trade.vt_orderid,
                order_trade.vt_symbol,
                order_trade.direction.value,
                order_trade.trade_diff,
                order_trade.trade_price,
                order_trade.reference
            )
            self.write_log(msg)
            self.update_trade_stats(order_trade, trade_volume)

        msg = ("ORDER {}[{}], ref={} source={},query_num={}, cancel_num={}, latest_query_time={}, "
               "latest_cancel_time={}").format(
            order_trade.status.value,
            order_trade.vt_orderid,
            order_trade.reference,
            order_trade.api_source,
            order_trade.query_num,
            order_trade.cancel_num,
            order_trade.latest_query_time,
            order_trade.latest_cancel_time
        )
        self.write_log(msg)

        if order_trade.is_finished():
            self.cleanup_finished_order(order_trade)
            if order_trade.status == Status.REJECTED:
                rejection_msg = "委托{}[{}]{}{}".format(
                    order_trade.status.value,
                    order_trade.vt_orderid,
                    order_trade.error_id,
                    order_trade.error_msg
                )
                self.write_log(rejection_msg)
                # 有拒单发送消息
                if order_trade.error_id != -1:
                    self.send_dingding(rejection_msg)

    def update_trade_stats(self, order_trade, trade_volume):
        direction = order_trade.direction
        trade_cost = trade_volume * order_trade.trade_price

        if direction == Direction.LONG:
            # print("on_order_trade", order_trade, trade_volume * order_trade.trade_price)
            self.symbol_traded[order_trade.direction] += trade_volume
            self.symbol_traded_cost[order_trade.direction] += trade_cost
            # print("on_order_trade", self.symbol_traded_cost[order_trade.direction])
        else:
            self.symbol_traded[order_trade.direction] -= trade_volume
            self.symbol_traded_cost[order_trade.direction] -= trade_cost

    def cleanup_finished_order(self, order_trade):
        vt_orderid = order_trade.vt_orderid
        if vt_orderid in self.strategy_order_trades:
            self.strategy_order_trades.remove(vt_orderid)

        if vt_orderid in self.to_recover_queue:
            self.to_recover_queue.remove(vt_orderid)

        order_dict = (self.buy_orderid_order_dict if order_trade.direction == Direction.LONG
                      else self.sell_orderid_order_dict)

        if vt_orderid in order_dict:
            try:
                del order_dict[vt_orderid]
                # self.write_log(
                #     f"{vt_orderid} removed from {'buy' if order_trade.direction == Direction.LONG else 'sell'} order dict")
            except Exception:
                msg = f"{order_trade.direction.value} order to be removed [{vt_orderid}], " \
                      f"{(order_trade.price, order_trade.volume, 1 if order_trade.direction == Direction.LONG else -1)}, " \
                      f"existing_{'buy' if order_trade.direction == Direction.LONG else 'sell'}_dict: {order_dict}"
                self.write_log(msg)
